﻿using System;
using System.Linq;
using CardPlay.Model.ActionModel;
using CardPlay.Model.CardModel;
using CardPlay.Model.GameModel;
using CardPlay.Model.GameModel.Actions;
using CardPlay.Model.GameModel.Presentation;
using CardPlay.Model.Layout;
using CardPlay.Model.Primitives;

namespace CardPlay.DemoPack.Games.CongressVariants
{
    [Game("36E491A3-317D-478A-B67E-FE7E15C8EF1C", LevelOfDifficulty.Easy, LevelOfSkill.ModerateSkill, DurationOfPlay.Within5To10Minutes, NumberOfDecks = 2, Family = Congress.FamilyName)]
    public class Babette : StandardCardGame
    {
        private const int TableauColumns = 8;

        private bool[,] _isTableauGap = new bool[TableauColumns, CardFace.RanksPerSuit];

        private class ResetTableauGapsAction : IAction
        {
            private readonly Babette _game;
            private bool[,] _resetState = new bool[TableauColumns, CardFace.RanksPerSuit];

            public ResetTableauGapsAction(Babette game)
            {
                _game = game;
            }

            public void Execute()
            {
                var temp = _game._isTableauGap;
                
                _game._isTableauGap = _resetState;
                _resetState = temp;
            }

            public void Reverse()
            {
                Execute();
            }
        }

        private class RemoveTableauCardAndRecordGapAction : RemovePileAction
        {
            private readonly Babette _game;
            private readonly bool _isTopCard;
            private readonly Point _gapRef;
    
            public RemoveTableauCardAndRecordGapAction(Babette game, PartLocation location) : base(game, location, 1)
            {
                var pile = game.PileAt(location);

                _game = game;
                _isTopCard = location.Index == pile.TopIndex;
                _gapRef = new Point(IndexOfTableau(pile), location.Index);
            }

            protected override void OnExecute()
            {
                base.OnExecute();

                if (! _isTopCard)
                {
                    for (var i = _gapRef.Y + 1; i < CardFace.RanksPerSuit - 1; ++i)
                        _game._isTableauGap[_gapRef.X, i] = _game._isTableauGap[_gapRef.X, i + 1];
                }

                _game._isTableauGap[_gapRef.X, _gapRef.Y] = true;
            }

            protected override void OnReverse()
            {
                if (! _isTopCard)
                {
                    for (var i = CardFace.RanksPerSuit - 1; i > _gapRef.Y; --i)
                        _game._isTableauGap[_gapRef.X, i] = _game._isTableauGap[_gapRef.X, i - 1];
                }
                
                _game._isTableauGap[_gapRef.X, _gapRef.Y] = false;

                base.OnReverse();
            }
        }

        public Babette()
        {
            NamesOfAdditionalPiles.Add(Stock);

            MaximumDeals = 2;
            TotalDecks = 2;
            TotalTableaus = TableauColumns;
            TotalFoundations = 8;
        }

        public override ILayoutSite CreatePresentation(IPresentationContext presentation)
        {
            var foundations = presentation.CreateRowLayout();
            presentation.AddPiles(foundations, PilesNamed(NamesOfFoundations.Take(4)), presentation.StyleForRepeatedRankFoundation(this, Rank.Ace, 1));
            presentation.AddPiles(foundations, PilesNamed(NamesOfFoundations.Skip(4)), presentation.StyleForRepeatedRankFoundation(this, Rank.King, 1));

            var tableauStyle = StandardStyles.Tableau;
            tableauStyle.EmptyCue = null;
            tableauStyle.Arrangement = StackTableau;

            var tableau = presentation.CreateRowLayout();
            presentation.AddPiles(tableau, NamesOfTableaus, tableauStyle);

            var root = new GridLayout
            {
                Columns = 2,
                Rows = 2,
                HorizontalContentAlignment = HorizontalAlignment.Center,
                //VerticalAlignment = VerticalAlignment.Stretch
            };

            root.Add(presentation.CreatePileVisual(Stock, presentation.StyleForStock(this)));
            root.Add(foundations);
            root.Add(presentation.CreateMimimalSpacer());
            root.Add(tableau);

            return root;
        }

        public override RemovePileAction CreatePickupActionAt(PartLocation location)
        {
            if (CardAt(location) != null)
            {
                if (location.Matches(AnyTableau))
                    return new RemoveTableauCardAndRecordGapAction(this, location);

                if (location.Matches(AnyFoundation))
                    return new RemovePileAction(this, location, 1);
            }

            return null;
        }

        protected override void PrepareDealScript(DealScript script)
        {
            script.DealToEach(NamesOfTableaus, "up");
            script.DealRemainingFaceDownTo(Stock);
        }

        protected override void OverridePlayingRules(PlayingRules playingRules)
        {
            playingRules.At(AnyTableau).Permit<MovePileAction>().Never();

            playingRules.At(AnyFoundation).Permit<RemovePileAction>().When(action =>
                action.SourcePile.Count > 1 && 
                action.IsActingOnTopCard() &&
                FindReversalCandidateFor(action.SourcePile) != null);
        }

        protected override bool AllowedToBuildFoundation(MovePileAction action)
        {
            return action.IsBuildingTwoDeckFoundation();
        }

        protected override bool AllowedToPickupAtTableau(RemovePileAction action)
        {
            return action.IsActingOnFaceUpTopCard() || _isTableauGap[IndexOfTableau(action.SourcePile), action.Location.Index + 1];
        }

        protected override IAction QueryActionAt(PartLocation location)
        {
            if (location.Matches(Stock))
            {
                if (!this[Stock].IsEmpty)
                    return location == this[Stock].Top ? new DealOutTableauAction(this, location) : null;

                if (!IsRedealPossible)
                    return null;

                var topOfStock = PilesNamed(AnyTableau).Aggregate(-1, (result, pile) => result + pile.Count);

                var action = new ComplexAction
                {
                    new CollectTableauAction(this, location),
                    new IncrementDealCountAction(this),
                    new ResetTableauGapsAction(this),
                    new DealOutTableauAction(this, new PartLocation(Stock, topOfStock))
                };

                return action;
            }

            return base.QueryActionAt(location);
        }

        private CardPile FindReversalCandidateFor(CardPile sourcePile)
        {
            if (!sourcePile.Location.Matches(AnyFoundation) || sourcePile.Count <= 1) return null;

            var candidates = StandardPileNames.RangePatternForFoundations(sourcePile.GetOrdinal() > 4 ? 1 : 5, 4);
            return PilesNamed(candidates).FirstOrDefault(targetPile => 
                (targetPile.IsEmpty && sourcePile.Count == CardFace.RanksPerSuit) || 
                (!targetPile.IsEmpty && CardFaceSequence.SameSuitAdjacentRank.IsSatisfiedBy(sourcePile.TopCard, targetPile.TopCard)));
        }

        private Size[] StackTableau(CardPile pile, LayoutMetrics metrics)
        {
            var result = new Size[pile.Count];
            if (String.IsNullOrEmpty(pile.Name)) return result;

            var column = IndexOfTableau(pile);

            for (var i = 1; i < pile.Count; ++i)
            {
                var offset = _isTableauGap[column, i]
                    ? metrics.MinSpacingDown + metrics.CardSize.Height
                    : metrics.StackingOffsetOnFaceUpCard.Height;

                result[i] = result[i - 1] + new Size(0, offset);
            }

            return result;
        }

        private static int IndexOfTableau(CardPile pile)
        {
            return (pile.GetOrdinal() - 1) % TableauColumns;
        }
    }
}